import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
import struct
import threading

class SerialCommunicator:
    def __init__(self, root):
        self.root = root
        self.root.title("串口通信工具V0.01")
        self.root.geometry("600x400")
        self.root.resizable(True, True)
        
        self.ser = None
        self.is_connected = False
        # 添加状态变量用于跟踪拍照命令的发送状态
        self.is_waiting_response = False      # 是否等待响应
        self.is_restart_required = False      # 是否需要重启板子
        self.is_awaiting_0x70_for_capture = False  # 是否等待0x70命令后发送拍照
        self.has_sent_capture_after_0x70 = False   # 是否已经在收到0x70后发送了拍照命令
        # 添加状态变量用于跟踪录像命令的发送状态
        self.is_awaiting_0x70_for_record = False    # 是否等待0x70命令后发送录像
        self.has_sent_record_after_0x70 = False     # 是否已经在收到0x70后发送了录像命令
        self.setup_ui()
        self.update_ports()
    
    def setup_ui(self):
        # 创建主框架
        main_frame = ttk.Frame(self.root, padding="20")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 串口设置区域
        port_frame = ttk.LabelFrame(main_frame, text="串口设置", padding="10")
        port_frame.pack(fill=tk.X, pady=10)
        
        ttk.Label(port_frame, text="端口:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        self.port_var = tk.StringVar()
        self.port_combo = ttk.Combobox(port_frame, textvariable=self.port_var, state="readonly", width=15)
        self.port_combo.grid(row=0, column=1, padx=5, pady=5)
        
        ttk.Label(port_frame, text="波特率:").grid(row=0, column=2, sticky=tk.W, padx=5, pady=5)
        self.baud_var = tk.StringVar(value="1500000")
        baud_combo = ttk.Combobox(port_frame, textvariable=self.baud_var, state="readonly", width=12)
        baud_combo['values'] = ('9600', '19200', '38400', '57600', '115200', '1500000')
        baud_combo.grid(row=0, column=3, padx=5, pady=5)
        
        self.refresh_btn = ttk.Button(port_frame, text="刷新端口", command=self.update_ports)
        self.refresh_btn.grid(row=0, column=4, padx=5, pady=5)
        
        self.connect_btn = ttk.Button(port_frame, text="连接", command=self.toggle_connection)
        self.connect_btn.grid(row=0, column=5, padx=5, pady=5)
        
        # 命令发送区域
        cmd_frame = ttk.LabelFrame(main_frame, text="命令控制", padding="10")
        cmd_frame.pack(fill=tk.X, pady=10)
        
        # 拍照按钮
        self.capture_btn = ttk.Button(cmd_frame, text="拍照 (0x01)", command=self.send_capture_command, state=tk.DISABLED)
        self.capture_btn.pack(side=tk.LEFT, padx=10, pady=10)
        
        # 录像设置
        record_frame = ttk.Frame(cmd_frame)
        record_frame.pack(side=tk.LEFT, padx=10, pady=10)
        
        ttk.Label(record_frame, text="录像时间 (秒):").pack(side=tk.LEFT, padx=5)
        self.record_time_var = tk.StringVar(value="60")
        self.record_time_entry = ttk.Entry(record_frame, textvariable=self.record_time_var, width=5)
        self.record_time_entry.pack(side=tk.LEFT, padx=5)
        
        self.record_btn = ttk.Button(record_frame, text="录像 (0x02)", command=self.send_record_command, state=tk.DISABLED)
        self.record_btn.pack(side=tk.LEFT, padx=5)
        
        # 停止录像按钮
        self.stop_record_btn = ttk.Button(cmd_frame, text="停止录像 (0x08)", command=self.send_stop_record_command, state=tk.DISABLED)
        self.stop_record_btn.pack(side=tk.LEFT, padx=10, pady=10)
        
        # 状态栏
        self.status_var = tk.StringVar(value="就绪")
        status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        
        # 日志区域
        log_frame = ttk.LabelFrame(main_frame, text="通信日志", padding="10")
        log_frame.pack(fill=tk.BOTH, expand=True, pady=10)
        
        # 日志控制按钮
        log_control_frame = ttk.Frame(log_frame)
        log_control_frame.pack(fill=tk.X, pady=(0, 5))
        
        self.clear_log_btn = ttk.Button(log_control_frame, text="清空日志", command=self.clear_log)
        self.clear_log_btn.pack(side=tk.RIGHT)
        
        # 日志文本区域
        log_content_frame = ttk.Frame(log_frame)
        log_content_frame.pack(fill=tk.BOTH, expand=True)
        
        self.log_text = tk.Text(log_content_frame, height=10, wrap=tk.WORD)
        self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        scrollbar = ttk.Scrollbar(log_content_frame, command=self.log_text.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.log_text.config(yscrollcommand=scrollbar.set)
    
    def update_ports(self):
        ports = serial.tools.list_ports.comports()
        port_list = [port.device for port in ports]
        self.port_combo['values'] = port_list
        if port_list:
            self.port_var.set(port_list[0])
    
    def toggle_connection(self):
        if not self.is_connected:
            self.connect_serial()
        else:
            self.disconnect_serial()
    
    def connect_serial(self):
        try:
            port = self.port_var.get()
            baudrate = int(self.baud_var.get())
            
            self.ser = serial.Serial(
                port=port,
                baudrate=baudrate,
                timeout=0.1
            )
            
            if self.ser.is_open:
                self.is_connected = True
                self.connect_btn.config(text="断开")
                self.capture_btn.config(state=tk.NORMAL)
                self.record_btn.config(state=tk.NORMAL)
                self.stop_record_btn.config(state=tk.NORMAL)
                self.status_var.set(f"已连接到 {port} @ {baudrate} bps")
                self.log(f"串口连接成功: {port} @ {baudrate} bps")
                
                # 启动接收线程
                self.receive_thread = threading.Thread(target=self.receive_data, daemon=True)
                self.receive_thread.start()
        except Exception as e:
            messagebox.showerror("连接错误", f"无法连接到串口: {str(e)}")
            self.log(f"连接失败: {str(e)}")
    
    def disconnect_serial(self):
        try:
            # 重置状态变量
            self.is_waiting_response = False
            self.is_restart_required = False
            self.is_awaiting_0x70_for_capture = False
            self.has_sent_capture_after_0x70 = False
            self.is_awaiting_0x70_for_record = False
            self.has_sent_record_after_0x70 = False
            
            if self.ser and self.ser.is_open:
                self.ser.close()
            
            self.is_connected = False
            self.connect_btn.config(text="连接")
            self.capture_btn.config(state=tk.DISABLED)
            self.record_btn.config(state=tk.DISABLED)
            self.stop_record_btn.config(state=tk.DISABLED)
            self.status_var.set("已断开连接")
            self.log("串口已断开")
        except Exception as e:
            messagebox.showerror("断开错误", f"断开连接时发生错误: {str(e)}")
    
    def send_capture_command(self):
        """触发拍照流程，设置等待0x70命令的标志，收到0x70后才会发送实际的拍照命令"""
        # 重置相关标志
        self.is_restart_required = False
        self.has_sent_capture_after_0x70 = False
        
        # 设置等待0x70命令的标志
        self.is_awaiting_0x70_for_capture = True
        
        # 显示提示信息
        self.log("已触发拍照操作，等待收到0x70命令后发送拍照指令...")
    
    def _send_actual_capture_command(self):
        """实际发送拍照命令的方法，只发送一次"""
        cmd = 0x01
        param_length = 0
        
        # 构建数据包: 帧头(0xaa) + 命令(0x01) + 参数长度(2字节，值为0) + 参数内容(无) + CRC(4字节0x00)
        packet = struct.pack('BBH', 0xAA, cmd, param_length)
        crc_bytes = b'\x00\x00\x00\x00'
        packet_with_crc = packet + crc_bytes
        
        try:
            if self.ser and self.ser.is_open:
                self.ser.write(packet_with_crc)
                self.log(f"发送拍照命令: {[hex(b) for b in packet_with_crc]}")
                self.is_waiting_response = True
            else:
                self.log("无法发送拍照命令：串口未连接")
        except Exception as e:
            self.log(f"发送拍照命令时出错: {str(e)}")
    
    # 移除_continuous_send_capture方法，不再需要持续发送拍照命令
    
    def send_record_command(self):
        """触发录像流程，设置等待0x70命令的标志，收到0x70后才会发送实际的录像命令"""
        # 重置相关标志
        self.is_restart_required = False
        self.has_sent_record_after_0x70 = False
        
        # 设置等待0x70命令的标志
        self.is_awaiting_0x70_for_record = True
        
        # 显示提示信息
        self.log("已触发录像操作，等待收到0x70命令后发送录像指令...")
    
    def _send_actual_record_command(self):
        """实际发送录像命令的方法，只发送一次"""
        cmd = 0x02
        param_length = 4  # 4字节时间参数
        
        try:
            record_time = int(self.record_time_var.get())
            if record_time < 0 or record_time > 1024:
                self.log(f"录像时间参数错误: {record_time}秒，必须在0-1024秒之间")
                return
            
            # 构建数据包: 帧头(0xaa) + 命令(0x02) + 参数长度(2字节，值为4，大端格式) + CRC(4字节0x00) + 参数内容(时间)
            # 按照正确顺序：帧头 + 命令 + 参数长度 + CRC + 时间值
            # 使用大端格式打包参数长度
            base_packet = struct.pack('>BBH', 0xAA, cmd, param_length)
            # 添加CRC
            crc_bytes = b'\x00\x00\x00\x00'
            # 添加时间参数，使用小端格式
            time_bytes = struct.pack('<I', record_time)  # 使用小端格式
            # 组合成完整数据包
            packet_with_crc = base_packet + crc_bytes + time_bytes
            
            if self.ser and self.ser.is_open:
                self.ser.write(packet_with_crc)
                self.log(f"发送录像命令: {[hex(b) for b in packet_with_crc]}")
                self.is_waiting_response = True
            else:
                self.log("无法发送录像命令：串口未连接")
        except ValueError:
            self.log(f"录像时间参数错误: {self.record_time_var.get()} 不是有效的数字")
        except Exception as e:
            self.log(f"发送录像命令时出错: {str(e)}")
    
    def send_packet(self, packet, command_name):
        try:
            if self.ser and self.ser.is_open:
                # 添加4个字节的CRC（默认都为0x00）
                crc_bytes = b'\x00\x00\x00\x00'
                packet_with_crc = packet + crc_bytes
                
                self.ser.write(packet_with_crc)
                self.log(f"发送{command_name}命令: {[hex(b) for b in packet_with_crc]}")
            else:
                messagebox.showwarning("未连接", "请先连接串口")
        except Exception as e:
            messagebox.showerror("发送错误", f"发送命令时发生错误: {str(e)}")
            self.log(f"发送失败: {str(e)}")
    
    def send_stop_record_command(self):
        """发送停止录像命令"""
        cmd = 0x08
        param_length = 0
        
        # 构建数据包: 帧头(0xaa) + 命令(0x08) + 参数长度(2字节，值为0) + CRC(4字节0x00)
        packet = struct.pack('>BBH', 0xAA, cmd, param_length)  # 参数长度使用大端格式
        crc_bytes = b'\x00\x00\x00\x00'
        packet_with_crc = packet + crc_bytes
        
        try:
            if self.ser and self.ser.is_open:
                self.ser.write(packet_with_crc)
                self.log(f"发送停止录像命令: {[hex(b) for b in packet_with_crc]}")
                self.is_waiting_response = True
            else:
                self.log("无法发送停止录像命令：串口未连接")
        except Exception as e:
            self.log(f"发送停止录像命令时出错: {str(e)}")
    
    def receive_data(self):
        """接收串口数据的线程函数，实现帧解析功能处理粘包情况"""
        buffer = bytearray()  # 缓冲区用于存储未处理的字节
        
        while self.is_connected:
            try:
                if self.ser and self.ser.in_waiting > 0:
                    # 读取所有可用数据到缓冲区
                    buffer.extend(self.ser.read(self.ser.in_waiting))
                    
                    # 尝试解析缓冲区中的帧
                    self.parse_frames(buffer)
            except Exception as e:
                if self.is_connected:  # 只有在连接状态下才报错
                    self.log(f"接收错误: {str(e)}")
                    self.disconnect_serial()
    
    def parse_frames(self, buffer):
        """解析缓冲区中的帧数据，每个帧以0xAA开头"""
        while len(buffer) > 0:
            # 查找0xAA帧头
            frame_start_index = buffer.find(0xAA)
            
            if frame_start_index == -1:
                # 没有找到帧头，清空缓冲区
                buffer.clear()
                break
            
            # 跳过帧头之前的所有数据
            if frame_start_index > 0:
                self.log(f"丢弃无效数据: {[hex(b) for b in buffer[:frame_start_index]]}")
                del buffer[:frame_start_index]
            
            # 确保有足够的数据来解析基本帧结构：帧头 + 命令 + 参数长度(2字节)
            if len(buffer) < 4:  # 0xAA + cmd(1字节) + param_length(2字节)
                break
            
            # 解析命令和参数长度
            cmd = buffer[1]
            param_length = (buffer[2] << 8) | buffer[3]  # 参数长度为2字节
            
            # 计算完整帧的长度：帧头(1) + 命令(1) + 参数长度(2) + 参数内容(param_length) + CRC(4)
            total_frame_length = 1 + 1 + 2 + param_length + 4
            
            if len(buffer) < total_frame_length:
                # 帧不完整，等待更多数据
                break
            
            # 提取完整的帧
            full_frame = buffer[:total_frame_length]
            
            # 从缓冲区删除已处理的帧
            del buffer[:total_frame_length]
            
            # 记录接收到的完整帧
            self.log(f"接收完整帧: {[hex(b) for b in full_frame]}")
            
            # 检查是否收到0x70命令
            if cmd == 0x70:
                self.log("收到0x70命令")
                
                # 如果正在等待0x70命令后发送拍照，并且还没有发送过，则发送一次拍照命令
                if self.is_awaiting_0x70_for_capture and not self.has_sent_capture_after_0x70:
                    self._send_actual_capture_command()
                    self.has_sent_capture_after_0x70 = True
                    self.is_awaiting_0x70_for_capture = False  # 完成后重置等待标志
                
                # 如果正在等待0x70命令后发送录像，并且还没有发送过，则发送一次录像命令
                if self.is_awaiting_0x70_for_record and not self.has_sent_record_after_0x70:
                    self._send_actual_record_command()
                    self.has_sent_record_after_0x70 = True
                    self.is_awaiting_0x70_for_record = False  # 完成后重置等待标志
            
            # 检查是否是拍照命令的响应
            elif cmd == 0x01:  # 拍照命令的响应
                # 重置等待响应标志
                self.is_waiting_response = False
                self.log("收到拍照命令响应")
                
            # 检查是否是停止录像命令的响应
            elif cmd == 0x08:  # 停止录像命令的响应
                # 重置等待响应标志
                self.is_waiting_response = False
                self.log("收到停止录像命令响应")
                
                # 检查是否是录像命令的响应
            elif cmd == 0x02:  # 录像命令的响应
                # 重置等待响应标志
                self.is_waiting_response = False
                self.log("收到录像命令响应")
                
                # 检查帧内容是否包含重启提示相关信息
                if param_length > 0:
                    # 提取参数内容进行分析
                    param_content = full_frame[4:4+param_length]  # 帧头(1) + cmd(1) + param_length(2) 后的内容
                    
                    # 尝试将参数内容转换为字符串进行检查
                    try:
                        param_str = param_content.decode('ascii', errors='ignore')
                        # 检查是否包含重启相关关键词
                        if '重启' in param_str or 'restart' in param_str.lower():
                            self.is_restart_required = True
                            self.root.after(0, lambda: messagebox.showinfo("提示", "请重启板子"))
                            self.log(f"检测到重启提示: {param_str}")
                    except Exception as e:
                        self.log(f"解析响应内容时出错: {str(e)}")
    
    def log(self, message):
        """添加日志信息"""
        self.log_text.insert(tk.END, message + "\n")
        self.log_text.see(tk.END)
    
    def clear_log(self):
        """清空日志内容"""
        self.log_text.delete(1.0, tk.END)
        self.log("日志已清空")

if __name__ == "__main__":
    root = tk.Tk()
    app = SerialCommunicator(root)
    
    # 窗口关闭时断开连接
    def on_closing():
        if app.is_connected:
            app.disconnect_serial()
        root.destroy()
    
    root.protocol("WM_DELETE_WINDOW", on_closing)
    root.mainloop()